package com.zndroid.logcat.displayer;

import android.Manifest;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.Toast;

import androidx.core.content.PermissionChecker;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.zndroid.logcat.Config;
import com.zndroid.logcat.R;
import com.zndroid.logcat.assist.OnClickListener;
import com.zndroid.logcat.assist.OnToastListener;
import com.zndroid.logcat.assist.XToast;
import com.zndroid.logcat.assist.draggable.MovingDraggable;
import com.zndroid.logcat.core.LogInfo;
import com.zndroid.logcat.core.LogcatManager;
import com.zndroid.logcat.core.LogcatMapper;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created by lzy on 2021/1/15.
 *
 * 日志显示窗体，以及操作管理
 */
public final class LogcatWindow extends XToast implements OnClickListener, OnToastListener, LogcatMapper.IMapper {
    private final List<LogInfo> dataList = new CopyOnWriteArrayList<>();
    /**原始数据备份*/
    private final List<LogInfo> backList = new CopyOnWriteArrayList<>();
    private final AtomicBoolean isZoomMax = new AtomicBoolean(false);
    private final AtomicBoolean isPause = new AtomicBoolean(false);
    private final AtomicBoolean isCanAutoScroll = new AtomicBoolean(true);

    private ExecutorService executor;

    private RecyclerView recyclerView;
    private LogcatAdapter adapter;
    private Spinner spinner;
    private EditText mSearchView;
    private ImageView emptyImageView;

    private String level = LogLevel.VERBOSE;

    public LogcatWindow(Application application) {
        super(application);
        initWindow(application);
    }

    public LogcatWindow(Activity activity) {
        super(activity);
        initWindow(activity);
    }

    private void initWindow(Context context) {
        setView(R.layout.layout_logcat_windows);
        setGravity(Gravity.CENTER);
        setDraggable(new MovingDraggable());
        //去除FLAG_NOT_FOCUSABLE属性，否则无法弹出软键盘
        clearWindowFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);

        zoomWindow(context, false);

        ////////////
        setOnClickListener(R.id.menu_close, this);
        setOnClickListener(R.id.menu_min, this);
        setOnClickListener(R.id.menu_zoom, this);

        setOnClickListener(R.id.tv_play_or_pause, this);
        setOnClickListener(R.id.tv_clean, this);
        setOnClickListener(R.id.tv_save, this);

        setOnClickListener(R.id.iv_log_empty, this);
        setOnClickListener(R.id.iv_scroll, this);

        setOnToastListener(this);
        ////////////

        initRecycleView(context);
        initSplit(context);
    }

    private void initSplit(Context context) {
        emptyImageView = getView().findViewById(R.id.iv_log_empty);

        mSearchView = getView().findViewById(R.id.et_log_search);
        mSearchView.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                String keyword = s.toString().trim();
                adapter.setKeyWord(keyword);

                emptyImageView.setVisibility("".equals(keyword) ? View.GONE : View.VISIBLE);
            }
        });

        String[] data = context.getResources().getStringArray(R.array.txt_priority);
        ArrayAdapter<String> levelAdapter = new ArrayAdapter<String>(context, R.layout.layout_spinner_item, data);

        spinner = getView().findViewById(R.id.sp_priority);
        spinner.setAdapter(levelAdapter);
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                level = data[position];

                adapter.clearData();
                for (LogInfo info : backList) {
                    checkToFilter(info);
                }

                if(adapter.getItemCount() > 0) {
                    recyclerView.scrollToPosition(adapter.getItemCount() - 1);
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });
    }

    private void zoomWindow(Context context, boolean max) {
        if (max) {
            setWidth(context.getResources().getDisplayMetrics().widthPixels);
            setHeight(context.getResources().getDisplayMetrics().heightPixels);
        } else {
            setWidth((int) (context.getResources().getDisplayMetrics().widthPixels * 0.85));
            setHeight((int) (context.getResources().getDisplayMetrics().heightPixels * 0.45));
        }
    }

    private void updateZoomIcon(View view, boolean max) {
        if (max) {
            view.setBackgroundResource(R.drawable.log_shape_zoom_pressed);
        } else {
            view.setBackgroundResource(R.drawable.log_shape_zoom_normal);
        }
    }

    private void updateReadIcon(View view, boolean read) {
        if (read) {
            view.setBackgroundResource(R.drawable.log_ic_resume);
        } else {
            view.setBackgroundResource(R.drawable.log_ic_pause);
        }
    }

    private void readOrNot(boolean read) {
        if (read) {
            LogcatManager.getInstance().resume();
        } else {
            LogcatManager.getInstance().pause();
        }
    }

    private void initRecycleView(Context context) {
        recyclerView = getView().findViewById(R.id.rv);

        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
        //从下往上刷新数据
        linearLayoutManager.setStackFromEnd(true);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);

                if (!recyclerView.canScrollVertically(1)) {
                    isCanAutoScroll.set(true);
                } else {
                    isCanAutoScroll.set(false);
                }
            }
        });


        adapter = new LogcatAdapter(dataList);
        recyclerView.setAdapter(adapter);
    }

    @Override
    public void onClick(XToast toast, View view) {
        int id = view.getId();
        if (-1 != id) {
            if (id == R.id.menu_min || id == R.id.menu_close) {
                cancel();
            }

            if (id == R.id.menu_zoom) {
                zoomWindow(view.getContext(), !isZoomMax.getAndSet(!isZoomMax.get()));
                updateZoomIcon(view, !isZoomMax.get());
            }

            if (id == R.id.tv_play_or_pause) {
                readOrNot(isPause.getAndSet(!isPause.get()));
                updateReadIcon(view, isPause.get());
                isCanAutoScroll.set(true);
            }

            if (id == R.id.tv_clean) {
                LogcatManager.getInstance().clean();
                isCanAutoScroll.set(true);
                dataList.clear();
                backList.clear();
                adapter.notifyDataSetChanged();
            }

            if (id == R.id.iv_log_empty) {
                mSearchView.setText("");
                adapter.setKeyWord("");
            }

            if (id == R.id.tv_save) {
                if (!TextUtils.isEmpty(Config.LOG_SAVE_DIR)) {
                    if (!checkPermission()) {
                        Toast.makeText(getContext(), getContext().getString(R.string.txt_logcat_save_permission_tip), Toast.LENGTH_SHORT).show();
                        return;
                    } else {
                        saveLogToLocal();
                    }
                } else {
                    //如果路径为空，取默认路径。即当前"data/data/your_app_package_name/files"
                    //后缀为：xxx_log.log文件  xxx为保存时间
                    saveLogToLocal();
                }
            }

            if (id == R.id.iv_scroll) {
                if(adapter.getItemCount() > 0) {
                    recyclerView.scrollToPosition(adapter.getItemCount() - 1);
                }
                isCanAutoScroll.set(true);
            }
        }

    }

    public void logStart() {
        isCanAutoScroll.set(true);
        LogcatManager.getInstance().start(this);
    }

    public void logStop() {
        dataList.clear();
        backList.clear();
        LogcatManager.getInstance().close();
        cancel();
    }

    @Override
    public void onShow(XToast toast) {
        logStart();
        //每次显示都重置为读取状态
        isPause.set(false);
        updateReadIcon(toast.getView().findViewById(R.id.tv_play_or_pause), isPause.get());
    }

    @Override
    public void onDismiss(XToast toast) {
        //nothing
        //只有依附的FloatingWindow消失才停止日志捕获，当前窗体消失为后台运行
    }

    @Override
    public void onConsole(LogInfo info) {
        //过滤当前应用打印
        if (Integer.parseInt(info.getProcessId()) == android.os.Process.myPid()) {
            recyclerView.post(new Runnable() {
                @Override
                public void run() {
                    backList.add(info);
                    checkToFilter(info);

                    if (isCanAutoScroll.get()) {
                        if(adapter.getItemCount() > 0) {
                            recyclerView.scrollToPosition(adapter.getItemCount() - 1);
                        }
                    }
                }
            });
        }
    }

    private void checkToFilter(LogInfo info) {
        String content = mSearchView.getText().toString();
        if ("".equals(content) && LogLevel.VERBOSE.equals(level)) {
            adapter.addItem(info);
        } else {
            if (LogLevel.VERBOSE.equals(level) || level.equalsIgnoreCase(info.getPriority())) {
                if (info.getOrigin().contains(content)) {
                    adapter.addItem(info);
                }
            }
        }
    }

    private boolean checkPermission() {
        // 如果当前应用适配的是 Android 11 及以上
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            return Environment.isExternalStorageManager();
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return PermissionChecker.checkSelfPermission(recyclerView.getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED &&
                    PermissionChecker.checkSelfPermission(recyclerView.getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED;
        } else {
            return true;
        }
    }

    private void saveLogToLocal() {
        Toast.makeText(getContext(), getContext().getString(R.string.txt_logcat_save_ing_tip), Toast.LENGTH_LONG).show();

        //最多同时保存3个日志文件
        //CallerRunsPolicy 重试添加当前的任务，他会自动重复调用execute()方法
        if (null == executor) {
            executor = new ThreadPoolExecutor(1, 1,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        }

        File saveFile;
        if (TextUtils.isEmpty(Config.LOG_SAVE_DIR)) {
            saveFile = new File(getContext().getFilesDir(), new SimpleDateFormat(Config.LOG_SAVE_FORMAT, Locale.getDefault()).format(new Date()) + Config.LOG_FILE_NAME);
        } else {
            File file = new File(Config.LOG_SAVE_DIR);
            if (!file.exists()) {
                file.mkdir();
            }

            saveFile = new File(Config.LOG_SAVE_DIR, new SimpleDateFormat(Config.LOG_SAVE_FORMAT, Locale.getDefault()).format(new Date()) + Config.LOG_FILE_NAME);
        }

        if (!executor.isShutdown()) {
            executor.execute(new LogSaveTask(saveFile, dataList));
        }
        executor.shutdown();
    }
}
